/* @flow */

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

export function initMixin (Vue: Class<Component>) {

  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.

      //如果是子组件,vm.propsData = {value:123},父组件的vnode会把props值传下来 
      //但是这里父vnode的props值更新如何触发子组件渲染这里不太清楚
      initInternalComponent(vm, options)
    } else {
      // 合并两个选项，出现相同配置项时，子选项会覆盖父选项的配置
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor), // 拿到全局默认配置 vm.constructor.options
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      //这里没啥用,如果vm.$options.render存在,就在vm身上做一层代理,发现取不存在的属性时爆出警告
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    //这里最重要的是建立父子级关系,如果发现有父级,就 parent.$children.push(vm),vm.$parent = parent,vm.$children = []

    //这里大概摸出了执行顺序,最开始是根节点执行_init创建vm对象,后代子组件也会依次调用_init创建自己的vm对象,initLifecycle就会把它们的父子级关系设置好
    initLifecycle(vm)
     /**
     * 初始化自定义事件，这里需要注意一点，所以我们在 <comp @click="handleClick" /> 上注册的事件，监听者不是父组件，
     * 而是子组件本身，也就是说事件的派发和监听者都是子组件本身，和父组件无关
     *  
     *   vm._events[event = "click"] || (vm._events[event] = [])).push(fn),fn就是写在组件件上面的handleClick，只不过错了一层捕捉错误的代理封装,最后被赋予子组件的_events对象上
     */
    initEvents(vm)
    //处理父组件的插槽信息,并结合子组件的插槽定义,获取插槽数据并赋值给子组件vm.$slots,定义vm._c和vm.$createElement
    initRender(vm)
    //是从vm.$options取出钩子函数调用,这个钩子函数是用户定义的,没定义不会执行
    callHook(vm, 'beforeCreate')
    //通过vm子组件定义的inject,通过vm.$parent层层往上寻找_provided,寻找与inject定义的key相匹配的value,组合成新对象result = {key:value,key:value},并将result设置在vm上(通过defineReactive),这样就可以在子组件上通过this.key拿到value
    initInjections(vm) // resolve injections before data/props
    
    //响应式处理 props,method,data,computed和watch
    initState(vm)

    //定义一个_provide属性,vm._provided = {key:value,key:value}
    initProvide(vm) // resolve provide after data/props
    
    callHook(vm, 'created')
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

export function resolveConstructorOptions (Ctor: Class<Component>) {

  //构造函数不知道在哪个地方被赋予了一个options
  /**
   *  options = {
   *    conponents,
   *    directive,
   *    filters:{},
   *    _base:Function 
   *  }     
   * 
   */
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }
  }
  return modified
}
